I'm trying to adapt the following react bar chart to my react/redux project : http://codepen.io/clindsey/collab/RRZBQm/. Most of the code is ok for me however I don't really understand the rendering part :
setTimeout(() => {
const data = {'alpha': 82, 'bravo': 20, 'charlie': 68};
const size = [480, 320];
const margins = [40, 40, 40, 40];
ReactDOM.render(
<BarChart {...{data, margins, size}} />
, document.getElementById('js-app'));
}, 0);
Why in this example, "const" are passed in ReactDOM.render function through the BarChart element ?
When adapting this to my local project I've got the following code in my container :
const App = () => (
<div>
<Component1 />
<Component2 />
<BarChart />
</div>
);
export default App;
then I use mapStateToProps function to pass const data, const size and const margins, like this :
const data = {'alpha': 82, 'bravo': 20, 'charlie': 68};
const size = [480, 320];
const margins = [40, 40, 40, 40];
function mapStateToProps(state) {
return {
data: data,
size: size,
margins: margins,
};
};
It's working fine but I didn't really understand what I'm doing. Not sure if It's good practice or it it's heaven make sense.
Thanks.
The whole idea of mapStateToProps is to link your Redux store to your (in your case) React Components. Typically you'd use it like this
function mapStateToProps(state){
return {
propertyOne: state.myPropertyOne, //state.myPropertyOne comes from your redux store, when you return this object, your component gets this object
propertyTwo: state.myPropertyTwo
};
}
You didn't have to pass your const variables into the function for it to work. If they were in that file you could have just used them directly.
Seems to work just fine
https://jsfiddle.net/julianljk/qkjkqf6q/
Related
I would like to find out how to write a functional component that would accept an array of objects as prop (dataSource), then render all the array objects based on a RenderFunction which I would pass as another prop. I want the render function to accept a parameter that would represent an object from the array.
Check this code snippet and the comments:
// This is my data
var dummyData = [{
"id": 1,
"name": "item1"
},
{
"id": 2,
"name": "item2"
},
{
"id": 3,
"name": "item3"
},
{
"id": 4,
"name": "item4"
},
{
"id": 5,
"name": "item5"
},
{
"id": 6,
"name": "item6"
}
]
// Functional Component :
function ListComponent(props) {
return (
<div className={"ListContainer"}>
{props.dataSource.map((x) => {
return (
<div key={x.id} className="ListItemContainer">
{props.itemRender}{" "}
</div>
);
})}{" "}
</div>
);
}
// This is my render function which I'm passing as a prop.
// I would like it to accept an argument which would become
// an object of dummy data array.
// For example if I passed x to it, the x inside the function on the first
// .map iteration would become {"id": 1,"name": "item1"}
function itemRenderTest() {
return (
<p> This is item </p>
// I want to be able to render it like this
// <p> This is {x.name} </p>
)
}
// passing props and rendering
ReactDOM.render(
<ListComponent
dataSource={dummyData}
itemRender={itemRenderTest()}
/>,
document.getElementById("root")
);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
The snippet illustrates some of the desired functionality. I can write a render function, but don't know how can I change the code so the render function could accept a parameter which would represent an object from the array.
I want to be able to write the render function like this:
function itemRenderTest(x){
return(
<p>This is {x.name}</p>
)
}
The Component would receive 2 props. The first - dataSrouce would specify the JSON array. The second - render function would define how the child list components are being rendered
<ListComponent
dataSource={dummyData}
itemRender={itemRenderTest}
/>
I'm trying to recreate a reusable component similar to what a lot of DevExtreme react components do. They basically just accept a render function definition like this renderItem={renderItemFunction} and it just works. I want to write my List component so it does the same This is a good example of one of their components
Is this possible with React? Code snippets would be really helpful.
That's 100% possible in React and a super common pattern. If I understand your question correctly -- what I typically do in this situation is
Define a parent component for the list items. It will handle fetching or otherwise retrieving the array of objects data, the overall state of that data, and the render logic for the individual list components
ListItem component, which is stateless (pure component) and simply renders reusable components based on data passed in as props. That's how component libraries create reusable components, like the one you mentioned
const ItemsList = () => {
// state variable which will store the list data
const [listData, setListData] = useState([])
// let's assume the list items are fetched from an API on initial render
useEffect(() => {
// fetch logic for list items, then update state variable
// with resulting data
const listItems = axios("https://yourapi.com/listitems")
.then(({ data }) => setListData(data)
.catch(err => console.info("Fetch error", err))
)
}, [])
const renderList = useMemo(() => listData.map(
(listItemData) => <ListItem data={listItemData}/>),
[listData])
return (
<div>
{renderList}
</div>
)
}
const ListItem = (props) => {
const { data } = props;
return (
// populate the component with the values from the data object
)
}
A few things to point out here:
useEffect hook with [] dependency will only run once on first render, retrieve the necessary data, and update the state variable initialized with the useState hook. This can be done in other ways too
useMemo hook allows us to define the render logic for the individual list components, and memoize the evaluated result. That way, this function won't run on every render, unless the value of the listData variable changes. The function provided to the useMemo hook iterates through the array of objects, and renders a ListItem components with the respective data
The ListItem component then simply receives the data as a prop and renders it
Edit based on the updated answer:
I haven't tested it but this approach should work.
const ItemsList = (props) => {
const { data, itemRender: ItemRender } = props;
const renderItems = () => data.map((itemData) => <ItemRender data={itemData}/>)
return (
<div>
{renderItems()}
</div>
)
}
const ListItem = (props) => {
const { data } = props;
return (
// populate the component with the values from the data object
)
}
const App = () => {
const data = retrieveData()
return (
<ItemsList data={data} itemRender={ListItem}/>
)
}
App component retrieves the data, decides on the component that it will use to render the individual item (ListItem) and passes both of those as props to ItemsList
ItemsList then simply maps the data to each individual component
Edit 2: basic working snippet
const ItemsList = (props) => {
const { data, itemRender: ItemRender } = props;
const renderItems = () => data.map((itemData, i) => <ItemRender data={itemData.val} key={i}/>)
return (
<div>
{renderItems()}
</div>
)
}
const ListItem = (props) => {
const { data } = props;
console.info(data)
return (
<div
style={{
width: 100,
height: 100,
border: "2px solid green",
display: "flex",
alignItems: "center",
justifyContent: "center"
}}>
{data}
</div>
)
}
const App = () => {
const data = [{val: 1}, {val: 2}, {val: 3}]
return (
<div
style={{
width: "100vw",
height: "100vh",
backgroundColor: "white",
display: "flex",
alignItems: "center",
justifyContent: "center"
}}>
<ItemsList data={data} itemRender={ListItem}/>
</div>
)
}
export default App;
I'm working on a configurable set of map layers with Deck.GL & React. I have a BaseMap component that I'll pass layers of data to as react children.
Currently, I have this:
BaseMap:
export const BaseMap = ({ latitude = 0, longitude = 0, zoom = 4, children }) => {
const deckProps = {
initialViewState: { latitude, longitude, zoom },
controller: true
};
return (
<DeckGL {...deckProps}>
{children}
<StaticMap />
</DeckGL>
);
};
And it's used like this:
<BaseMap>
<ScatterplotLayer
data={scatterData}
getPosition={getPositionFn}
getRadius={1}
radiusUnits={'pixels'}
radiusMinPixels={1}
radiusMaxPixels={100}
filled={true}
getFillColor={[255, 255, 255]}
/>
<TextLayer
data={textData}
getPosition={getPositionFn}
getColor={[255, 0, 0]}
getText={getTextFn}
/>
</BaseMap>
This is okay, but I want to add default props to each child.
Attempt 1
I've tried this in BaseMap, but get the error cannot assign to read only property props of object #<Object>:
...
return (
<DeckGL {...deckProps}>
{React.Children.map(children, (c) => {
const defaultProps = {
loaders: [CSVLoader]
}
c.props = { ...defaultProps, ...c.props };
return c;
})}
</DeckGL>
);
Attempt 2
I've also tried creating a wrapper component for each type of layer, but get the error Cannot call a class as a function:
wrapper:
export const ScatterplotLayerWrapper = (props) => {
const defaultScatterProps = {
loaders: [CSVLoader]
};
const scatterLayerProps = {
...defaultScatterProps,
...props
};
return <ScatterplotLayer {...scatterLayerProps} />;
};
used like this:
<BaseMap>
<ScatterplotLayerWrapper
data={scatterData}
getPosition={getPositionFn}
/>
</BaseMap>
I suspect the problem with this second attempt has something to do with the caveat here.
Solution?
I can imagine two types of solutions (and obviously, there may be others!):
correct method for checking the layer type & modifying child props depending on the type, or something similar - is this possible?
or
Some way to convince react/deck.gl that ScatterplotLayer will be a child of Deck.GL, even if it isn't in ScatterplotLayerWrapper. (This one seems less likely)
The confusion came from a mis-understanding of how deck.gl's React component works & what those <ScatterplotLayer> components really are (they're not react components).
Deck.gl's react component, DeckGL, intercepts all children and determines if they are in fact "layers masquerading as react elements" (see code). It then builds layers from each of those "elements" and passes them back to DeckGL's layers property.
They look like react components, but really aren't. They can't be rendered on their own in a React context. They can't be rendered outside of the DeckGL component at all, because they're still just plain deck.gl layers.
The solution here is to create a new map layer class just like you might in any other context (not a React component wrapping a layer). Docs for that are here.
class WrappedTextLayer extends CompositeLayer {
renderLayers() { // a method of `Layer` classes
// special logic here
return [new TextLayer(this.props)];
}
}
WrappedTextLayer.layerName = 'WrappedTextLayer';
WrappedTextLayer.defaultProps = {
getText: (): string => 'x',
getSize: (): number => 32,
getColor: [255, 255, 255]
};
export { WrappedTextLayer };
This new layer can then be used in the BaseMap component (or the un-wrapped DeckGL component`) like this:
<BaseMap>
<WrappedTextLayer
data={dataUrl}
getPosition={(d) => [d.longitude, d.latitude]}
/>
</BaseMap>
In addition, the exact same layer can be passed to DeckGL as a layer prop:
<DeckGL
layers={[
new WrappedTextLayer({
data: dataUrl,
getPosition: (d) => [d.longitude, d.latitude]
})
]}
></DeckGL>
Modifying the BaseMap component a little will allow it to accept layers either as JSX-like children, or via the layers prop as well:
export const BaseMap = ({ latitude = 0, longitude = 0, zoom = 4, children, layers }) => {
const deckProps = {
initialViewState: { latitude, longitude, zoom },
controller: true,
layers
};
return (
<DeckGL {...deckProps}>
{children && !layers ? children : null}
<StaticMap />
</DeckGL>
);
};
I'm having a problem about the download of the kendo react chart as an image,
currently the download works but only for my latest chart (I have six of them)
I've recreated the error in stackblitz
As you can see whenever I try to download one of the 2 charts the downloaded one is always the latest one
Is there any way for fixing this?
The problem is that refContainer is being set twice inside your App component in the example you linked. One time for each of your charts. The reference will always refer to the second chart, because the second chart overwrites the value of refContainer last.
What you can do instead is to create a CustomChart component that holds its own ref (refContainer). This way you can render multiple instances of this component, without the refs clashing. This also allows us to get rid of some duplicate code for creating the chart.
So you can do something like this:
import * as React from "react";
import {
Chart,
ChartSeries,
ChartSeriesItem,
ChartCategoryAxis,
ChartCategoryAxisItem,
exportVisual,
} from "#progress/kendo-react-charts";
import { exportImage } from "#progress/kendo-drawing";
import { saveAs } from "#progress/kendo-file-saver";
const CustomChart = ({ categories, data }) => {
let refContainer = React.useRef(null);
const onExportVisual = () => {
const chartVisual = exportVisual(refContainer);
if (chartVisual) {
exportImage(chartVisual).then((dataURI) => saveAs(dataURI, "chart.png"));
}
};
return (
<>
<button onClick={() => onExportVisual()}>Export as visual element</button>
<Chart ref={(chart) => (refContainer = chart)}>
<ChartCategoryAxis>
<ChartCategoryAxisItem categories={categories} />
</ChartCategoryAxis>
<ChartSeries>
<ChartSeriesItem data={data} />
</ChartSeries>
</Chart>
</>
);
};
const App = () => {
return (
<div>
<CustomChart
categories={[2015, 2016, 2017, 2018]}
data={[10, 100, 100, 10]}
/>
<CustomChart
categories={[2015, 2016, 2017, 2018]}
data={[100, 10, 10, 100]}
/>
</div>
);
};
export default App;
This is the parent Component.
export default function CanvasPanel(props) {
const [sketchCoordinates, setSketchCoordinates] = useState([]);
useEffect(() => {
console.log(sketchCoordinates);
}, [sketchCoordinates]);
const onAddPoint = function (mousePos) {
setSketchCoordinates([
...sketchCoordinates,
{ x: mousePos.x, y: mousePos.y }
]);
};
const onPrintObject = (toPrint) => {
console.log(toPrint);
};
return (
<div className="canvas-panel">
Points:
<hr />
{sketchCoordinates.join(", ")}
<Canvas
sketchCurveCoordinates={sketchCoordinates}
addPoint={onAddPoint}
printObject={onPrintObject}
/>
<Sidepanel createPoint={onAddPoint} />
</div>
);
}
This is the child Component
export default function Canvas(props) {
const getMousePos = function (event) {
const offset = canvasRef.current.getBoundingClientRect();
return {
x: event.clientX - offset.left,
y: event.clientY - offset.top
};
};
const handleOnMouseUp = (event) => {
const mousePos = getMousePos(event);
props.printObject(mousePos);
props.addPoint(mousePos);
};
return (
<div className="canvas-container">
<PureCanvas
handleOnMouseUp={handleOnMouseUp}
/>
</div>
);
};
This is the PureCanvas component
export default class PureCanvas extends Component {
shouldComponentUpdate() {
return false;
}
render() {
return (
<canvas
className="canvas"
width="750"
height="500"
onMouseUp={this.props.handleOnMouseUp}
></canvas>
);
}
}
The code is not complete as I removed all canvas related stuff because it doesn't really matter, but it has enough to explain and reproduce the behaviour. The addPoint function should append an object with x and y properties to the sketchCoordinates array through spread operator and the update function from useState. This works when calling addPoint({x: (any int), y: ({any int}) at the CanvasPanel component, but when calling at the Canvas component through props it works only once. The array appends the first element but then it just replaces that same element with a new one, when it should append that element. So, I know I could do this with classes but I can't understand why it won't work with hooks. The following image shows the console after some updates to de sketchCoordinates array:
Console
EDIT
Please consider this code sandbox for further enlightenment.
This doesn't appear to be an issue with anything specific to the hook: if we replace the <canvas> with a button that just calls handleOnMouseUp on click, it works as expected.
I suspect it's not anything to do with the hook, and perhaps it's some detail that's been simplified out of your example:
Here's a code sandbox with this simplified version, that replaces the canvas with just a button.
I am using useContext as a global state solution. I have a Store.jsx which contains my state, and a reducer.jsx which reduces. I am using Konva to create some shapes on an HTML5 Canvas. My goal is when I click on a shape I want to update my global state with a reference to what is active, and when I click again, to clear the reference.
My Full Code can be found here:
https://codesandbox.io/s/staging-platform-2li83?file=/src/App.jsx
Problem:
The problem is when I update the global state via the onClick event of a shape, its says that the reference is 'null', but when I console.log the reference in the onClick I can see the correct reference.
I think I am missing an important point to how useRef works.
This is how the flow appears in my head when I think about this:
I create a canvas, and I map an array of rectangle properties. This creates 4 rectangles. I use a wrapper component that returns a rectangle.
{rectArray.map((rectangle, index) => {
return (
<RectWrapper key={index} rectangle={rectangle} index={index} />
);
})}
Inside the RectWrapper, I create a reference, pass it to the ref prop of the Rect. In the onclick function, when I console log 'shapeRef' I see the refence ONLY when dispatch is commented out. If I uncomment dispatch then it shows as null, and if I console log the state, the reference is always null.
const RectWrapper = ({ rectangle, index }) => {
const shapeRef = React.useRef();
return (
<Rect
x={rectangle.x + index * 100}
y={5}
width={50}
height={50}
fill="red"
ref={shapeRef}
onClick={() => {
console.log("ShapeRef: ");
console.log(shapeRef); // This correctly identifies the rect only when dispatch is uncommented
dispatch({
type: "active_image",
payload: {
index: index,
reference: shapeRef
}
});
}}
/>
);
};
perhaps I am going about this to wrong way with hooks. I am just trying to keep a global state of whats been clicked on because components in another file would rely on this state.
The problem is happening because you are creating RectWrapper component as a functional component within your App component causing a new reference of the component to be created again and again and thus the reference is lost
Move your RectWrapper into a separate component declared outside of App component and pass on dispatch as a prop to it
import React, { useEffect, useContext, useState, Component } from "react";
import { Stage, Layer, Rect, Transformer } from "react-konva";
import { Context } from "./Store.jsx";
import "./styles.css";
const RectWrapper = ({ rectangle, index, dispatch }) => {
const shapeRef = React.useRef();
return (
<Rect
x={rectangle.x + index * 100}
y={5}
width={50}
height={50}
fill="red"
ref={shapeRef}
onClick={() => {
console.log("ShapeRef: ");
console.log(shapeRef);
dispatch({
type: "active_image",
payload: {
index: index,
reference: shapeRef
}
});
}}
/>
);
};
export default function App() {
const [state, dispatch] = useContext(Context);
console.log("Global State:");
console.log(state);
const rectArray = [
{ x: 10, y: 10 },
{ x: 10, y: 10 },
{ x: 10, y: 10 },
{ x: 10, y: 10 }
];
return (
<div className="App">
<Stage height={500} width={500}>
<Layer>
{rectArray.map((rectangle, index) => {
return (
<RectWrapper
dispatch={dispatch}
key={index}
rectangle={rectangle}
index={index}
/>
);
})}
</Layer>
</Stage>
</div>
);
}
Working demo
I don't think you need to create a ref in RectWrapper, because onClick has one event parameter. And the ref of the element that was clicked can be found in the event:
onClick={(e) => {
const thisRef = e.target;
console.log(thisRef );
...
Here is a working version without useRef: https://codesandbox.io/s/peaceful-brook-je8qo