Do you know WordPress, this tool that powers 27% of the web? This big old CMS everyone loves to hate because It’s oldish, it’s PHP and it’s not an SPA? but in the same time, everyone uses because it’s so great at achieving the goal it’s designed for: creating content? Well! It’s time to stop criticizing and take a look at what’s happening in the WordPress community and what we’ll get for the next iteration of our beloved CMS? (more…)
Tag: ReactJS
-
When we hear about GraphQL, it’s often mentioned as the new way to retrieve data from the server. The silver bullet that will replace REST APIs. This is true, GraphQL can do an amazing job to run the new generation of server APIs but this is not the whole picture.
From the homepage of the official website, GraphQL is described as “A query language for your API” where you:
- First, describe your data
- Then, ask for what you want
- Finally, obtain predictable results
We quickly make the assumption that it allows us to query server APIs using HTTP requests but if we take a deeper look at the official JavaScript implementation, we understand that this is not totally true, GraphQL is a query language for any Data Provider whether it’s local or remote.
Many Tools have been developed to ease the use of GraphQL server-side: From Relay, Apollo Data Tools, express-graphql etc… but few people focused on using it on the client. While you’ll probably get the most of it, if it’s used server-side, using GraphQL on the client can be valuable as well and often, using GraphQL on the server requires a big investment where you probably have to rewrite your entire application to make the transition.
A typical React/Redux application
In the React Ecosystem, a large number of applications are using Redux to store their data in a global state tree. How can we use GraphQL to improve the architecture of such applications without rewriting our application from scratch?
So first, let’s try to take a look at a typical React/Redux Application (A Todo Application of course). We’ll probably have:
- A reducer that “stores” the data in the global state:
todos
reducer, - An action creator used to retrieve todos from the server:
fetchTodos
In most cases, This action creator will rely on
redux-thunk
to trigger a network request and dispatch the action once the results retrieved.- A selector
getTodos
responsible of retrieving thetodos
from the global state
And to link all of these items together, we would use
react-redux
to bring our data and action creators to our component. A completeTodoList
React Component could look like that:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { Component } from 'react'; import { connect } from 'react-redux'; import { fetchTodos } from './actions'; import { getTodos } from './selectors'; class TodoList extends Component { componentWillMount() { this.props.fetchTodos(); } render() { const { todos } = this.props; return ( <div> { todos.map( todo => <Todo key={ todo.id } text={ todo.text } done={ todo.done } /> ) } </div> ); } } const TodoListContainer = connect( state => { return { todos: getTodos( state ); }; }, { fetchTodos } )( TodoList ); export default TodoListContainer; Declarative data fetching
Let’s take a deeper look at this component. It has three responsibilities:
- It triggers an action creator to fetch Data
- It uses a selector to retrieve Data from the redux tree
- And it renders the Data
React’s main purpose is providing a declarative API, to express how your data get transformed into DOM elements, but in our example above, the fetching and data retrieval is imperative. This means it’s the responsibility of the persons who write the component to trigger the data fetching requests and eventually handles data refreshing. And in this area, Relay has been doing a great job to transform these imperative calls into a declarative API. What if we could provide a declarative API (similar to Relay) without the need of rewriting our APIs to use GraphQL? and without even using Relay?
We want our component to be written like this:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { Component } from 'react'; import { query } from 'react-graphql-redux'; const TodoList = ( { data: { todos } } ) => { return ( <div> { todos.map( todo => <Todo key={ todo.id } text={ todo.text } done={ todo.done } /> ) } </div> ); }; export default query(` { todos { id text done } ) `)( TodoList ); If we need more data such as the authenticated user, our query could be:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// … our component export default query( { todos { id text done } user { isLoggedin username } `)( TodoList ); Basically, we use a Higher Order Component called
query
to declare that our component needs a list of todos, and for each todo, we want its id, text, and done attributes. We don’t mind how this data is fetched from the server or retrieved from the redux store, we just declare what data the component needs and it will be made available as a prop.How is this possible?
Ok! this sounds awesome right! but isn’t this too difficult to achieve, how do we match each part of this query to its corresponding actions / selectors?
We need a tree resolution algorithm where we match each node of the tree to a function that fetches and retrieves this data. And you know!! this is exactly the purpose of GraphQL. A way to match a
query
toresolvers
.Ok! Let’s setup GraphQL to resolve the query above:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const createGraph = store => { // A GraphQL schema (used to validate the data) const schema = ` type Todo { id: Int, text: String, done: Boolean } type Query { todos: [Todo] } `; // The resolver of our todos tree node const todosResolver = () => { // Trigger the fetch action creator const state = store.getState(); const todos = getTodos( state ); if ( ! todos ) store.dispatch( fetchTodos() ); // Return retrieved data from the store return todos; }; // The root of our GraphQL setup const root = { hello: () => 'hello world', todos: todosResolver, // Append any other todos here }; return { schema, root }; } The idea here is for every node of our query, we write:
1- A schema: A good side effect of using GraphQL is the schema which will be used to validate our data. This ensures a valid data is provided to all your React Components.
2- A resolver: A function with the following shape:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const myResolver = ( resolverArgs ) => { // If the data is not present or is old, let's trigger a refresh if ( shouldIRefreshTheData( resolverArgs ) ) { store.dispatch( fetchData( resolverArgs ) ); } // Retrieve the data from the state const state = store.getState(); return myDataSelector( state ); } Make our GraphQL resolvers available for the query HoC
Now that our GraphQL setup is ready, we can make it available on the React context, that way the
query
HoC can use it to provide data to the wrapped components. This is the exact same technique used byreact-redux
to make the store available for connected components.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { render } from 'react-dom'; import { GraphProvider } from 'react-graphql-redux'; import App from './app'; // create your redux store const store = // … // define your GraphQL schema and root (see above) const { schema, root } = createGraph( store ); // render your app wrapped in the GraphProvider render( <GraphProvider store={ store } schema={ schema } root={ root }> <App /> </GraphProvider> , document.getElementById('root') ); Now, you are ready to go! wrap your components with
query
HoC.Is this worth it?
If you’re working on a small application with dozen of actions and selectors, probably not. Just stick with the imperative fetching, but If your application is big enough, I would say, this is certainly worth a try.
Good benefits
I already mentioned the fact that using GraphQL allows you to validate your data, you are certain that the data provided to your components would always be valid against the defined schema.
Another good side effect is the possibility to use GraphiQL. If you’re not familiar with it yet, it’s a web interface which allows you to test your GraphQL queries before using them in your components. Also, it allows you to navigate through your data which increases data discoverability a lot. You won’t lose your time looking or rewriting existing redux selectors, if the data is already available, you’ll just find it in GraphiQL.
Library
You can find the
query
HoC and theGraphProvider
in this library, but it’s easy enough to write on your own if you prefer to.Who is using this approach already?
Me:). I don’t know if anyone else is but we’re considering using it for Calypso, the new WordPress.com front-end. Here is the PR
Time to get your hands dirty! and let me know if you have any thoughts or concerns on this 🙂
-
I have to admit, It took me some time to figure out in which use cases ES6 generators could be really useful (I’m not the only one I think, am I ?). When we talk about ES6 (or ES2015 as you like), generators are not the first feature we think about, we often see them as an easy way to create custom iterators, but in reality they are much more than that, they are maybe one of the most interesting features added in ES6. (more…)
-
There’s this expression I say each time I stumble on something (some technology) that’s going to change my habits in development …. Oh, ça déchire sa mère 😛 a not so conventional french alternative to wow that’s great !!!.And you know what, It just happened again. I discovered redux-saga, And I’m like: “Wow this is great, that’s exactly what I was looking for”