Updating colors dynamically using React Hooks? - reactjs

I'm trying to update the scene background dynamically using a npm package that's called 'react-colorful'.
So far it works great, debugging the color picker with console.log() returns desired colors but Three.js doesn't seem to update it when I change it using the hooks from React.
So my goal is to dynamically update the scene.background color when using the color picker. The color picker has no issues, returns everything dynamically when I debugged it.
Here is what I tried so far, if you need more information, feel free to ask.
My code is heavily based on this CodePen
const ThreePromoKitAnim = () => {
const [currBackground, setBackground] = useState('#ff0000')
const mount = useRef()
useEffect(() => {
// initialization of scene, camera
mount.current.appendChild(renderer.domElement)
}, [])
// value = returned color value from the color picker
const handleBackgroundChange = (value) => {
setBackground(value)
scene.background = new THREE.Color(currBackground)
}
return (
<>
<HexColorPicker
color={currBackground}
onChange={handleBackgroundChange}
/>
<div className='threejs' ref={mount} />
</>
)
}
Also, I've noticed that when React auto updates the page (when saving code) and I set another value of the background color using the color picker it does work but not dynamically.
Am I missing something? Why doesn't the background change dynamically? How can I use hooks to dynamically change the background? Is there another workaround to dynamically change the color?
Many thanks in advance.

The following code works for me, I just had to re-render the scene.
const handleBackgroundChange = useCallback((value) => {
setBackground(value)
scene.background = new THREE.Color(value)
renderer.render(scene, camera)
}, [scene])

Related

React event listener callback functions don't use updated states

When accessed from reDraw the resizableList is an empty array, when accessed from addImageClick it shows the actual array. the TextInput element contains a text input which calls eventBus.dispatch('addtext') on change.
So, after I add an image, I have two TextInput elements, and two Resizable elements. I change the text in one of the TextInput elements, empty state array. I trigger the add image button which logs the array before resetting it, array has two elements in it before reset.
import React,{useState,useRef,useEffect} from "react";
import Resizable from "./Resizable";
import TextInput from "./TextInput";
import eventBus from "../eventbus/EventBus";
export default function Meme(){
const [image, setImage] = useState(new Image());
const [resizableList, setResizableList] = useState([]);
const hiddenFileInput = useRef(null);
const cvs = useRef(null);
function reDraw(){
const width = 600*(image.width/image.height);
const ctx = cvs.current.getContext("2d");
ctx.clearRect(0, 0, 1200, 600);
ctx.drawImage(image,(1200-(width))/2,0,width,600);
console.log(resizableList);
for (let i=0;i<resizableList.length;i++){
let el = document.getElementById(`${i}--textbox`);
console.log('test');
}
console.log('ran');
console.log(image);
}
function addResizable(width){
let resizable = {
cvs:cvs,
imgwidth:width,
image:image
}
setResizableList((prevResizableList)=>[...prevResizableList, resizable]);
}
function draw() {
const width = 600*(image.width/image.height);
const ctx = cvs.current.getContext("2d");
console.log(image.width);
ctx.clearRect(0, 0, 1200, 600);
ctx.drawImage(image,(1200-(width))/2,0,width,600);
addResizable(width);
addResizable(width);
};
function addImageClick(e){
e.preventDefault();
console.log(resizableList);
setResizableList([]);
hiddenFileInput.current.click();
};
useEffect(()=>{
eventBus.on('addtext',reDraw);
image.onload = draw;
console.log('useeffect');
},[])
return (
<main>
<form className="form">
<div className='form--textboxes'>
{resizableList.map((item,index)=>{
return (<TextInput cvs={item.cvs} image={item.image} imgwidth={item.imgwidth} key={index} id={index}/>)
})}
<button className='form--button--textboxes'>Add Text</button>
</div>
<button className="form--button" onClick={addImageClick}>Add Image</button>
<div className="form--image">
<canvas id='meme' ref={cvs} className="form--meme" height="600" width="1200"></canvas>
{resizableList.map((item,index)=>{
return (<Resizable cvs={item.cvs} imgwidth={item.imgwidth} key={index} id={index}/>)
})}
</div>
</form>
<input
type="file"
name="myImage"
style={{display:'none'}}
ref={hiddenFileInput}
onChange={(event) => {
image.src = URL.createObjectURL(event.target.files[0]);
}}
/>
</main>
)
};
I apologize if the explanation above is too long. So far I've been able to get by just by reviewing questions, it's the first time I actually had to write one.
Update: What I basically want to do is loop over the resizableList array, and update the text on the canvas based on the position and size of each resizable. I know how to do that but I can't, because when I access the array from the reDraw function, the array shows as empty. I tried looking for alternatives but I couldn't find any. I create an id for each textbox/resizable of format: arrayindex--textbox and arrayindex--box. That's how the textboxes and resizables relate to eachother.
2nd Update: Sooooooo...
function addTextClick(e){
e.preventDefault();
console.log(resizableList);
}
function reDraw(){
const width = 600*(image.width/image.height);
const ctx = cvs.current.getContext("2d");
ctx.clearRect(0, 0, 1200, 600);
ctx.drawImage(image,(1200-(width))/2,0,width,600);
console.log(resizableList);
button.current.click();
}
As I said previously, reDraw is triggered by an event fired by the text input's onChange. I also made this button which has the addTextClick as it's onClick. And yes, I trigger input's onchange, I get one empty array followed by one length 2 array(which is the resizableList array) in my console. ?????
I guess this kinda fixes my problem as I can just create a hidden button but this is more of a workaround and I would still like to understand why this is happening.
Updated with solution:
So, I ran into this same issue while working on some other functionality of my webapp.
The problem is that when you add an event listener the callback function is set with the state values present at the time. So it doesn't matter how many times your states are updated after setting the event listener, it's still going to use the states it had when it was set up.
Funny enough, after understanding what was causing the problem I found some posts from others running into this problem and using exactly the same workaround I used above, which is to create a reference to a hidden button and have the event listener's callback function trigger the onClick.
Another solution is to use the useEffect hook and recreate the event listener every time a state is updated, but in my case that doesn't really work that well since I have a lot of states that are updated constantly.

SPFx ListView control - "defaultSelection" does not work

I'm using a ListView control from #pnp/spfx-controls-react library version 2.5.0
There is a documented property called defaultSelection which is:
The index of the items to be select by default
I'm trying to use it like this to autoselect the first row of the table:
<ListView
items={this.state.items}
viewFields={this._viewFields}
selectionMode={SelectionMode.single}
selection={this._updateSelectedItems}
defaultSelection={[0]}
/>
But when the page loads it never selects anything. Also no errors in the console.
I've also used a bare example found here: https://github.com/RaspeR87/sp-dev-fx-webparts/tree/master/spfx-react-controls/ListView to try it on a simple project but again it never selects anything.
Did anyone manage to make use of this property? Every input appreciated.
Here is a hack to solve the problem:
// Add a useRef to your ListView control
const refListView = useRef<any>(null);
// Set selection on load
useEffect(() => {
// Set mySelection as default selection
const mySelection = [1,4,6];
mySelection.forEach(i => {
setTimeout(() => {
refDocs.current._selection.setIndexSelected(i, true, false);
}, 0);
});
}, []);
// ListView control
<ListView ref={refListView }...
Yes, I have the same test result as yours. You may post this issue in the PNP react control Github repository.https://github.com/pnp/sp-dev-fx-controls-react/issues

Conditional styling on row for dynamic cell value

Conditional row styling on ag grid where I want to do rowstyle on user choice of cell value
gridoptions.getRowStyle = function(params) {
if (params.node.data === 'cell value typed by user in external/custom component i.e outside grid') {
return { 'background': value selected by user in cutom componet outside grid };
}
}
#sandeep's answer works perfectly. I just want to chime in another way to solve the problem which is to use context. context is just another javascript object which contains any information that you want to share within AgGrid. The data will be accessible in most AgGrid callbacks for example cell renderers, editors's render callback and in your case getRowStyle callback
const sickDays = // data from external component
const color = // data from external component
<AgGridReact
getRowStyle={(params) => {
const { styles, data } = params.context;
if (params.node.data["sickDays"] === data.sickDays) {
return { backgroundColor: styles.color };
}
return null;
}}
context={{
data: { sickDays },
styles: { color }
}}
/>
Live Demo
here is a plunkr which should give you idea to solve the problem. since i don't know much about your component hence i used two input boxes with button to set background color to row but you can use complex styles as well.
I am using api.redrawRows() since the operation we are performing needs to work on row.

React color ChromePicker alpha slider does not work. Is required use Alpha individual API?

Im trying to implement react-color from https://casesandberg.github.io/react-color/#examples
I'm using the ChromePicker component, but the Alpha slider is not working, as expected.
The slider bar is working, but the slider bullet is not moving on.
Here is the https://codesandbox.io/s/react-color-nkh7w example. Is there any component missing?
The problem is that react-color components (like the ChromePicker you are using) do not accept properties for alpha/opacity directly. You have two options:
Using the color.rgb value only, and don't separate the alpha. If you pass the rgb object as the color property), it should work as expected.
You can take the color.rgb.a value, convert it to hex, and append it to the color.hex string.
color.hex doesn't contain the alpha value. you can you color.rgba instead, and also you can convert it to hex with rgb-hex package to make it displayable:
import { ChromePicker } from "react-color";
import rgbHex from "rgb-hex";
const App = () => {
const [color, setColor] = useState("#fff");
return <>
<ChromePicker
color={color}
onChange={c =>
setColor("#" + rgbHex(c.rgb.r, c.rgb.g, c.rgb.b, c.rgb.a))
}
/>
<p>You picked {color}</p>
</>
}

How does document.getSelection work under reactjs environment?

I was working on a electron-react project for epub file. Right now, I am planning to make the app capable of selecting text field and highlight it.
To achieve it, I was trying to use web's Window.getSelection api. However, there are some really weird things come up like in this.
In short, I am unable to capture the Selection object. It seems like even if I log the Selection object, this object could somehow jump to something else. Also, I cannot even serialize the Selection object with JSON.stringfy. This is super surprising, and this is my first time seeing something like this (I will get empty object to stringfy the Selection object).
So how to use Window.getSelection properly under react-electron environment? Or this api will not work well for text content which is generated by react's dangerouslySetInnerHTML?
Looks like the window.getSelection api needs to toString the selected object.
const getSelectionHandler = () => {
const selection = window.getSelection();
console.log("Got selection", selection.toString());
};
Super simple textarea and button to get selection react demo
this solution might help someone, catch last selection
const selection = useRef('');
const handleSelection = () => {
const text = document.getSelection().toString();
if (text) {
selection.current = text;
}
}
useEffect(() => {
document.addEventListener('selectionchange', handleSelection);
return () => {
document.removeEventListener('selectionchange', handleSelection);
};
});

Resources