I have a main component that contains 2 sub-components. A d3 graph component and a form component to edit the graph.
The data is loaded from a server in componentWillMount in the graph component. All resources show that data should be loaded in the componentDidMount lifecycle. But the data is used in the render method to render the nodes and links for the d3 graph. So if I load the data in componentDidMount, an error will be thrown, because I map over the data in here. How should this be handled with React?
Another thing that feels sketchy is that I query the database and then use that data to set the state. I do this because I need to change some data. The id's stored in the database aren't ordered linear. If an item with id 3 gets deleted it's gone. But d3 still wants id-number 3. d3 can't skip a number, say 3, and go on to the next number, 4. Is there a better way of doing this without setting the query to state?
When I use the form to add a node to the graph, I need to refresh the page before I can see the changes. I refetch the query using apollo's refetchQueries, but this doesn't work. Should this.props and nextProps be compared in componentDidUpdate to rerender the component and see the changes without refresh?
I know there are three questions in here, that's because I'm very unsure about which part causes exactly what problem. I'm not even sure what exactly the problem is and the possible ways to get around these problems. I hope you can give me an idea on how to deal with one of the things mentioned above, so I can try something new. I am trying to learn how to code, but being all alone on this subject really makes it difficult sometimes.
This is the main component:
class Main extends Component {
render() {
const nodes = this.props.Nodes.nodes;
const relationships = this.props.Relationships.relationships;
if (!relationships) { return <div> </div> }
if (!nodes) { return <div> </div> }
return (
<div>
<Forms/>
<Graph nodes={nodes} rels={relationships} />
</div>
);
}
}
export default compose(
graphql(Relationships, {
name: "Relationships"
}),
graphql(Nodes, {
name: "Nodes"
})
)(Main);
And this is the Graph component:
class Graph extends Component {
constructor(props) {
super(props);
this.state = { nodes: {}, links: {} };
}
loadData() {
const nodes = this.props.nodes;
const relationships = this.props.rels;
const graph = {};
graph.nodes = [];
graph.edges = [];
nodes.map(node => (
graph.nodes.push({ name: node.name, id: node.id, label: node.label })
));
relationships.map(rel => (
graph.edges.push({ source: parseInt(rel.source, 10), target: parseInt(rel.target, 10) })
));
const resetIds = [];
graph.edges.map(function (e) {
let sourceNode = graph.nodes.filter(function (n) {
return n.id === e.source;
})[0],
targetNode = graph.nodes.filter(function (n) {
return n.id === e.target;
})[0];
preedges.push({source: sourceNode, target: targetNode});
return preedges
});
this.setState({nodes: graph.nodes, links: resetIds });
}
componentWillMount(){
this.loadData();
}
componentDidMount() {
this.d3Graph = d3.select(ReactDOM.findDOMNode(this));
MYGRAPH.force = d3.forceSimulation(this.state.nodes)
.force("charge", d3.forceManyBody().strength(-50))
.force("link", d3.forceLink(this.state.links).distance(90))
.force("center", d3.forceCenter().x(MYGRAPH.width / 2).y(MYGRAPH.height / 2))
.force("collide", d3.forceCollide([30]).iterations([10]));
MYGRAPH.force.on('tick', () => {
this.d3Graph.call(MYGRAPH.updateGraph)
});
MYGRAPH.drag();
}
render() {
const nodes = this.state.nodes.map( (node) => {
return (
<Node
data={node}
label={node.label}
key={node.id}
/>);
});
const links = this.state.links.map( (link,i) => {
return (
<Link
key={link.target+i}
data={link}
/>);
});
return (
<div>
<svg width={MYGRAPH.width} height={MYGRAPH.height}>
<g >
{nodes}
</g>
<g >
{links}
</g>
</svg>
</div>
);
}
}
export default Graph;